﻿#pragma once
#include <string>
#include <vector>
#include <map>
#include <Windows.h>
#include <winhttp.h>
#include <regex>

#pragma comment(lib, "winhttp")

class CWinHttp
{
protected:

	HINTERNET			m_hSession;
	HINTERNET			m_hConnect;
	HINTERNET			m_hRequest;

	std::wstring		m_strHost;
	std::wstring		m_strPath;
	std::wstring		m_strExt;
	INTERNET_SCHEME		m_nScheme;
	INTERNET_PORT		m_nPort;

	std::vector<std::wstring> split(const std::wstring &str, std::wstring delimiter);

public:

	CWinHttp();

	//初始化Session
	BOOL OpenSession(
		const std::wstring &userAgent = L"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3756.400",
		int dwAccessType = WINHTTP_ACCESS_TYPE_NO_PROXY,
		const std::wstring &proxyName = L"",
		const std::wstring &proxyBypass = L"",
		int dwFlags = 0
	);

	//连接服务器
	BOOL OpenConnect(const std::wstring &url);

	//打开请求
	BOOL OpenRequest(bool bPost = false, bool bInitContentType = false);

	//发送数据
	BOOL Send(LPVOID lpbuffer = NULL, DWORD dwsize = 0);

	//上传文件多步提交,写入数据
	BOOL Write(LPCVOID lpbuffer, DWORD dwsize);

	//获取返回内容
	std::vector<BYTE> GetResponseBody();

	//获取指定返回协议头
	std::wstring GetResponseHeaderValue(int dwInfoLevel, DWORD dwIndex = WINHTTP_NO_HEADER_INDEX);
	std::wstring GetResponseHeaderValue(const std::wstring &name);
	std::wstring GetAllResponseHeaderValue(std::wstring &strHeaders, std::wstring key);

	//获取全部返回协议头
	std::wstring GetResponseHeaders();

	//设置请求协议头
	BOOL SetRequestHeader(const std::wstring &strHeader);
	BOOL SetRequestHeader(const std::wstring &name,const std::wstring &value);

	//设置请求referer
	BOOL SetReferer(const std::wstring &referer);

	//设置超时时间 OpenSession 以后调用
	BOOL SetTimeout(
		int nResolveTimeout = 3000,
		int nConnectTimeout = 3000,
		int nSendTimeout = 3000,
		int nReceiveTimeout = 3000
	);

	//设置属性
	BOOL SetOption(int Option, int value);

	//是否允许重定向 false不允许 true 允许
	BOOL SetLocal(bool Is);

	//取重定向地址
	std::wstring GetLocal();

	//设置Cookie
	BOOL SetCookie(const std::wstring &cookies);

	//获取返回cookies
	std::wstring GetCookie();

	//合并Cookies
	std::wstring MergeCookie(const std::wstring &oldCookies, const std::wstring &nowCookies);

	virtual ~CWinHttp();
};

CWinHttp::CWinHttp() { }

CWinHttp::~CWinHttp()
{
	if (m_hRequest) WinHttpCloseHandle(m_hRequest);
	if (m_hConnect) WinHttpCloseHandle(m_hConnect);
	if (m_hSession) WinHttpCloseHandle(m_hSession);
}

BOOL CWinHttp::OpenSession(const std::wstring &userAgent, int dwAccessType, const std::wstring &proxyName, const std::wstring &proxyBypass, int dwFlags)
{
	LPWSTR pwszUserAgent = NULL;
	if (!userAgent.empty()) {
		pwszUserAgent = const_cast<LPWSTR>(userAgent.c_str());
	}

	LPWSTR pwszProxyName = NULL;
	if (!proxyName.empty()) {
		pwszProxyName = const_cast<LPWSTR>(proxyName.c_str());
	}

	LPWSTR pwszProxyBypass = NULL;
	if (!proxyBypass.empty()) {
		pwszProxyBypass = const_cast<LPWSTR>(proxyBypass.c_str());
	}

	if (dwAccessType == WINHTTP_ACCESS_TYPE_NO_PROXY)
		m_hSession = WinHttpOpen(pwszUserAgent, dwAccessType, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, dwFlags); //不使用代理IP
	else
		m_hSession = WinHttpOpen(pwszUserAgent, dwAccessType, pwszProxyName, pwszProxyBypass, dwFlags); //使用代理IP
	return m_hSession != NULL;
}

BOOL CWinHttp::OpenConnect(const std::wstring &url)
{
	if (m_hSession)
	{
		if (WinHttpCheckPlatform())
		{
			URL_COMPONENTS		urlComp;
			ZeroMemory(&urlComp, sizeof(urlComp));
			urlComp.dwStructSize = sizeof(urlComp);

			WCHAR wszScheme[64] = { 0 };
			urlComp.lpszScheme = wszScheme;
			urlComp.dwSchemeLength = ARRAYSIZE(wszScheme);
			WCHAR wszHostName[1024] = { 0 };
			urlComp.lpszHostName = wszHostName;
			urlComp.dwHostNameLength = ARRAYSIZE(wszHostName);
			WCHAR wszUrlPath[1024] = { 0 };
			urlComp.lpszUrlPath = wszUrlPath;
			urlComp.dwUrlPathLength = ARRAYSIZE(wszUrlPath);
			WCHAR wszExtraInfo[1024] = { 0 };
			urlComp.lpszExtraInfo = wszExtraInfo;
			urlComp.dwExtraInfoLength = ARRAYSIZE(wszExtraInfo);

			std::wstring wurl;
			if (!std::regex_search(url, std::wregex(L"^https?://", std::wregex::icase)))
				wurl = L"http://" + url;
			else
				wurl = url;
			if (WinHttpCrackUrl(wurl.c_str(), wurl.length(), ICU_ESCAPE, &urlComp))
			{
				m_strHost = urlComp.lpszHostName;
				m_strPath = urlComp.lpszUrlPath;
				m_strExt = urlComp.lpszExtraInfo;
				m_nPort = urlComp.nPort;
				m_nScheme = urlComp.nScheme;
				m_hConnect = WinHttpConnect(m_hSession, m_strHost.c_str(), m_nPort, 0);
				return m_hConnect != NULL;
			}
		}
	}
	return FALSE;
}

BOOL CWinHttp::OpenRequest(bool bPost, bool bInitContentType)
{
	if (m_hConnect)
	{
		const wchar_t* pwszVerb = bPost ? L"POST" : L"GET";
		DWORD dwFlags = m_nScheme == INTERNET_SCHEME_HTTPS ? WINHTTP_FLAG_SECURE : 0;
		m_hRequest = WinHttpOpenRequest(m_hConnect, pwszVerb, m_strPath.c_str(), NULL,
			WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, dwFlags);
		if (bPost && bInitContentType)
			SetRequestHeader(L"Content-Type", L"application/x-www-form-urlencoded");
		return m_hRequest != NULL;
	}
	return FALSE;
}

BOOL CWinHttp::Send(LPVOID lpbuffer, DWORD dwsize)
{
	BOOL  bResults = FALSE;
	if (m_hRequest) {
		if (lpbuffer != NULL && dwsize > 0)
			bResults = WinHttpSendRequest(m_hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, lpbuffer, dwsize, dwsize, 0);
		else
			bResults = WinHttpSendRequest(m_hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0);
	}
	return bResults;
}

BOOL CWinHttp::Write(LPCVOID lpbuffer, DWORD dwsize)
{
	if (m_hRequest)
	{
		return WinHttpWriteData(m_hRequest, lpbuffer, dwsize, NULL);
	}
	return FALSE;
}

std::vector<BYTE> CWinHttp::GetResponseBody()
{
	DWORD dwSize = 0;
	DWORD dwDownloaded = 0;

	std::vector<BYTE> list;
	size_t listsize = 0, begin = 0;

	if (WinHttpReceiveResponse(m_hRequest, NULL))
	{
		do
		{
			// Check for available data.
			dwSize = 0;
			if (!WinHttpQueryDataAvailable(m_hRequest, &dwSize))
			{
				printf("Error %u in WinHttpQueryDataAvailable.\n", GetLastError());
				break;
			}

			// No more available data.
			if (!dwSize)
				break;

			begin = listsize;

			// Allocate space for the buffer.
			list.resize(listsize += dwSize);
			if (!WinHttpReadData(m_hRequest, &list[begin], dwSize, &dwDownloaded)) {
				printf("Error %u in WinHttpReadData.\n", GetLastError());
			}
		} while (dwSize > 0 && dwDownloaded > 0);
	}
	return list;
}

std::wstring CWinHttp::GetResponseHeaderValue(int dwInfoLevel, DWORD dwIndex)
{
	DWORD dwSize = 0;
	LPVOID lpOutBuffer = NULL;
	BOOL  bResults = FALSE;
	bResults = WinHttpQueryHeaders(m_hRequest, dwInfoLevel, WINHTTP_HEADER_NAME_BY_INDEX, NULL, &dwSize, &dwIndex);
	if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
	{
		lpOutBuffer = new WCHAR[dwSize / sizeof(WCHAR)];
		bResults = WinHttpQueryHeaders(m_hRequest, dwInfoLevel, WINHTTP_HEADER_NAME_BY_INDEX, lpOutBuffer, &dwSize, &dwIndex);
		return (wchar_t*)lpOutBuffer;
	}
	return L"";
}

std::wstring CWinHttp::GetResponseHeaderValue(const std::wstring &name)
{
	DWORD dwSize = 0;
	DWORD dwIndex = 0;
	LPVOID lpOutBuffer = NULL;

	BOOL  bResults = FALSE;
	
	bResults = WinHttpQueryHeaders(m_hRequest, WINHTTP_QUERY_CUSTOM, name.c_str(), NULL, &dwSize, &dwIndex);
	if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
	{
		lpOutBuffer = new WCHAR[dwSize / sizeof(WCHAR)];
		bResults = WinHttpQueryHeaders(m_hRequest, WINHTTP_QUERY_CUSTOM, name.c_str(), lpOutBuffer, &dwSize, &dwIndex);
		return (wchar_t*)lpOutBuffer;
	}
	return L"";
}

std::wstring CWinHttp::GetResponseHeaders()
{
	return GetResponseHeaderValue(WINHTTP_QUERY_RAW_HEADERS_CRLF);
}

BOOL CWinHttp::SetRequestHeader(const std::wstring &name, const std::wstring &value)
{
	if (name.empty() || value.empty())
		return FALSE;
	BOOL  bResults = FALSE;
	if (m_hRequest)
	{
		std::wstring strHeader(name + L": " + value);
		LPWSTR pwszHeaders = NULL;
		if (!strHeader.empty()) {
			pwszHeaders = const_cast<LPWSTR>(strHeader.c_str());
			bResults = WinHttpAddRequestHeaders(m_hRequest, pwszHeaders, (ULONG)-1L, WINHTTP_ADDREQ_FLAG_ADD_IF_NEW | WINHTTP_ADDREQ_FLAG_REPLACE);
		}
	}
	return bResults;
}

BOOL CWinHttp::SetRequestHeader(const std::wstring &strHeader)
{
	if (strHeader.empty())
		return FALSE;
	BOOL  bResults = FALSE;
	if (m_hRequest)
	{
		LPWSTR pwszHeaders = NULL;
		pwszHeaders = const_cast<LPWSTR>(strHeader.c_str());
		bResults = WinHttpAddRequestHeaders(m_hRequest, pwszHeaders, (ULONG)-1L, WINHTTP_ADDREQ_FLAG_ADD_IF_NEW | WINHTTP_ADDREQ_FLAG_REPLACE);
	}
	return bResults;
}

BOOL CWinHttp::SetReferer(const std::wstring &referer)
{
	return(SetRequestHeader(L"Referer", referer));
}

BOOL CWinHttp::SetTimeout(int nResolveTimeout, int nConnectTimeout, int nSendTimeout, int nReceiveTimeout)
{
	if (m_hSession)
		return WinHttpSetTimeouts(m_hSession, nResolveTimeout, nConnectTimeout, nSendTimeout, nReceiveTimeout);
	return FALSE;
}

BOOL CWinHttp::SetOption(int Option, int value)
{
	if (m_hRequest)
		return WinHttpSetOption(m_hRequest, Option, (LPVOID)&value, 4);
	return FALSE;
}

BOOL CWinHttp::SetLocal(bool Is)
{
	return SetOption(WINHTTP_OPTION_REDIRECT_POLICY, Is ? WINHTTP_OPTION_REDIRECT_POLICY_ALWAYS : WINHTTP_OPTION_REDIRECT_POLICY_NEVER);
}

std::wstring CWinHttp::GetLocal()
{
	return GetResponseHeaderValue(WINHTTP_QUERY_LOCATION);
}

BOOL CWinHttp::SetCookie(const std::wstring &cookies)
{
	return(SetRequestHeader(L"cookie", cookies));
}

std::wstring CWinHttp::GetCookie()
{
	std::wstring strHeaders;
	strHeaders = GetResponseHeaders();

	return std::move(GetAllResponseHeaderValue(strHeaders, L"Set-Cookie"));
}

std::wstring CWinHttp::GetAllResponseHeaderValue(std::wstring &strHeaders, std::wstring key)
{
	if (key.empty()) { return L""; }
	std::wstring res;
	std::map<std::wstring, std::wstring> map_res;
	std::map<std::wstring, std::wstring>::iterator iter;

	std::wsmatch mstr;
	std::vector<std::wstring> list;
	std::wstring::const_iterator iterStart = strHeaders.begin();
	std::wstring::const_iterator iterEnd = strHeaders.end();
	while (std::regex_search(iterStart, iterEnd, mstr, std::wregex(L"^\\S*" + key + L":\\s*(.+)", std::wregex::icase)))
	{
		list.push_back(mstr[1]);
		iterStart = mstr[0].second;
	}
	std::vector<std::wstring> array_list;

	for (size_t i = 0; i < list.size(); ++i) {
		std::vector<std::wstring> temp_list = split(list[i], L";");
		array_list.insert(array_list.end(), temp_list.begin(), temp_list.end());
	}

	for (size_t i = 0; i < array_list.size(); ++i) {
		size_t p = array_list[i].find(L"=");
		std::wstring name, value;
		if (p != std::wstring::npos) {
			name = array_list[i].substr(0, p);
			value = array_list[i].substr(p + 1);
		}
		else {
			name = std::regex_replace(array_list[i], std::wregex(L"^\\s+(.+)\\s+$"), L"$1");
			if (res.empty())
				res.append(name);
			else
				res.append(L"; ").append(name);
			continue;
		}
		name = std::regex_replace(name, std::wregex(L"^\\s*(.+?)\\s*$"), L"$1");
		value = std::regex_replace(value, std::wregex(L"^\\s*(.+?)\\s*$"), L"$1");
		if (!name.empty() && !value.empty())
		{
			iter = map_res.find(name);
			if (iter != map_res.end())
				iter->second = value;
			else
				map_res.insert(make_pair(name, value));
		}
	}

	for (iter = map_res.begin(); iter != map_res.end(); ++iter)
	{
		if (iter->second == L"-" || iter->second == L"''")
			continue;

		if (res.empty())
			res.append(iter->first).append(L"=").append(iter->second);
		else
			res.append(L"; ").append(iter->first).append(L"=").append(iter->second);
	}
	return res;
}

std::wstring CWinHttp::MergeCookie(const std::wstring &oldCookies, const std::wstring &nowCookies)
{
	std::map<std::wstring, std::wstring> map_cookies;
	std::map<std::wstring, std::wstring>::iterator iter;

	std::vector<std::wstring> array_list = split(oldCookies, L";");
	std::vector<std::wstring> now_array_list = split(nowCookies, L";");

	array_list.insert(array_list.end(), now_array_list.begin(), now_array_list.end());

	for (size_t i = 0; i < array_list.size(); ++i) {
		size_t p = array_list[i].find(L"=");
		std::wstring name, value;
		if (p != std::wstring::npos) {
			name = array_list[i].substr(0, p);
			value = array_list[i].substr(p + 1);
		}
		else {
			name = array_list[i];
		}

		name = std::regex_replace(name, std::wregex(L"^\\s+"), L"");
		name = std::regex_replace(name, std::wregex(L"\\s+$"), L"");
		if (!name.empty() && !value.empty())
		{
			iter = map_cookies.find(name);
			if (iter != map_cookies.end())
				iter->second = value;
			else
				map_cookies.insert(make_pair(name, value));
		}
	}

	std::wstring cookies;

	for (iter = map_cookies.begin(); iter != map_cookies.end(); ++iter)
	{
		if (iter->second == L"-" || iter->second == L"''")
			continue;

		if (cookies.empty())
			cookies.append(iter->first).append(L"=").append(iter->second);
		else
			cookies.append(L"; ").append(iter->first).append(L"=").append(iter->second);
	}
	return cookies;
}

inline std::vector<std::wstring> CWinHttp::split(const std::wstring &str, std::wstring delimiter)
{
	std::vector<std::wstring> res;
	size_t Start = 0, end = 0;
	while (Start < str.size()) {
		end = str.find(delimiter, Start);
		if (std::wstring::npos == end) {
			res.push_back(str.substr(Start)); break;
		}
		else {
			res.push_back(str.substr(Start, end - Start));
			Start = end + 1;
		}
	}
	return std::move(res);
}

class Request
{
public:
	Request(const std::wstring &url) {
		m_WinHttp.OpenSession();
		m_WinHttp.SetTimeout();
		if (!m_WinHttp.OpenConnect(url))
			throw "OpenConnect fail";
	}
	~Request() {}

	class Response
	{
	public:
		Response(CWinHttp &http) : http_(&http) {
			content = http.GetResponseBody();
			headers = http.GetResponseHeaders();
			std::wsmatch res;
			if (std::regex_search(headers, res, std::wregex(L"HTTP/[\\d\\.]+\\s+(\\d+)", std::wregex::icase)))
				status = _wtoi(std::wstring(res[1]).data());
			if (std::regex_search(headers, res, std::wregex(L"charset=(.+)", std::wregex::icase)))
				encoding = res[1];
		}
		~Response() { }

		std::wstring operator[](const std::wstring &key) {
			return http_->GetAllResponseHeaderValue(headers, key);
		}

		std::string operator[](const std::string &key) {
			return w2m(http_->GetAllResponseHeaderValue(headers, m2w(key)));
		}
		
		std::wstring to_wstring() {
			if (encoding == L"utf-8")
				return m2w(std::string(content.begin(), content.end()));
			else if (encoding == L"gb2312")
				return m2w(std::string(content.begin(), content.end()), 936);
			else if (encoding == L"big5")
				return m2w(std::string(content.begin(), content.end()), 950);
			else if (encoding == L"iso-8859-1")
				return m2w(std::string(content.begin(), content.end()), 28591);
			else
				return L"";
		}

		std::string to_string() {
			if (encoding == L"utf-8")
				return w2m(to_wstring());
			else if (encoding == L"gb2312")
				return std::string(content.begin(), content.end());
			else if (encoding == L"big5")
				return w2m(to_wstring());
			else if (encoding == L"iso-8859-1")
				return w2m(to_wstring());
			else
				return "";
		}

		std::wstring m2w(const std::string &str, UINT codepage = CP_UTF8) {
			int u16len = MultiByteToWideChar(codepage, NULL, str.data(), str.size(), NULL, NULL);
			if (u16len + 1 > buffer.wbuflen) {
				delete[] buffer.wbuf;
				buffer.wbuflen = 2 * (u16len + 1);
				buffer.wbuf = new wchar_t[buffer.wbuflen];
			}
			MultiByteToWideChar(codepage, NULL, str.data(), str.size(), buffer.wbuf, u16len);
			buffer.wbuf[u16len] = '\0';
			return buffer.wbuf;
		}

		std::string w2m(const std::wstring &str, UINT codepage = CP_ACP) {
			int sLen = WideCharToMultiByte(codepage, NULL, str.data(), str.size(), NULL, 0, NULL, NULL);
			if (sLen + 1 > buffer.buflen) {
				delete[] buffer.buf;
				buffer.buflen = 2 * (sLen + 1);
				buffer.buf = new char[buffer.buflen];
			}
			WideCharToMultiByte(codepage, NULL, str.data(), str.size(), buffer.buf, sLen, NULL, NULL);
			buffer.buf[sLen] = '\0';
			return buffer.buf;
		}
	public:
		size_t status = 0;
		std::wstring headers;
		std::wstring encoding = L"unknown";
		std::vector<BYTE> content;
	private:
		CWinHttp *http_;
		struct _BUF
		{
			char *buf = new char[128];
			int buflen = 128;
			wchar_t *wbuf = new wchar_t[128];
			int wbuflen = 128;
			~_BUF() {
				delete[] buf;
				delete[] wbuf;
			}
		} buffer;
	};
	Response get() {
		if (!m_WinHttp.OpenRequest())
			throw "OpenRequest fail";
		for (auto it : headers) {
			m_WinHttp.SetRequestHeader(it.first + L": " + it.second);
		}
		
		if (!referer.empty())
			m_WinHttp.SetReferer(referer);
		//m_WinHttp.SetLocal(false);
		m_WinHttp.Send();
		return Response(m_WinHttp);
	}

	template<typename PTR>
	Response post(PTR lpbuffer, DWORD dwsize) {
		if (!m_WinHttp.OpenRequest(true))
			throw "OpenRequest fail";
		for (auto it : headers) {
			m_WinHttp.SetRequestHeader(it.first + L": " + it.second);
		}
		if (!referer.empty())
			m_WinHttp.SetReferer(referer);
		if (!lpbuffer || !dwsize)
			m_WinHttp.Send();
		else
			m_WinHttp.Send((LPVOID)lpbuffer, dwsize);
		return Response(m_WinHttp);
	}

	template<typename T>
	Response post(std::vector<T> &data) {
		if (data.size() == 0) {
			return std::move(post(NULL, 0));
		}
		else {
			T p;
			for (size_t i = 0; i < data.size(); i++) {
				if (data[i].empty())
					continue;
				if (i > 0)
					p += '&';
				p += data[i];
			}
			return std::move(post(p.data(), p.size() * sizeof(p[0])));
		}
	}

public:
	std::map<std::wstring, std::wstring> headers = { };
	std::wstring referer = L"";

private:
	CWinHttp m_WinHttp;
};
